﻿//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//  Dispatcher.cs
//    
// Abstract:
//
//  This module is responsible for dispatching enhanced reminders to their
//  appropriate destinations.
//  

using System;
using System.Diagnostics;
using System.Security.Permissions;
using Microsoft.OfficeCommunicationsServer.Applications.Common;

namespace Microsoft.EnhancedReminders.Dispatcher
{
    /// <summary>
    /// The dispatcher is responsible for managing the delivery of enhanced
    /// reminders.
    /// </summary>
    public class NotificationDispatcher
    {
        private DispatcherClassFactory _factory;

        /// <summary>
        /// Event thrown when a delivery attempt has completed successfully or unsuccessfully.
        /// </summary>
        public event EventHandler<DeliveryCompletedEventArgs> DeliverNotificationCompleted;

        /// <summary>
        /// ctor.
        /// </summary>
        /// <param name="factory">Factory class for generating classes that have external
        /// dependencies. This allows for easier unit and functional testing fo th</param>
        public NotificationDispatcher(DispatcherClassFactory factory)
        {
            Debug.Assert(factory != null, "Cannot pass in null factory class.");

            _factory = factory;
        }

        /// <summary>
        /// Asynchronously attempt to deliver a notification to the appropriate channel(s).
        /// </summary>
        [EnvironmentPermissionAttribute(SecurityAction.LinkDemand, Unrestricted=true)]
        public void DeliverNotificationAsync(NotificationItem notification)
        {
            Debug.Assert(notification != null, "notification != null");
            Debug.Assert(notification.Id != null, "notification.Id != null");

            IPresenceProvider provider = _factory.CreatePresenceProvider();
            provider.GetPresenceCompleted += BeginDelivery;

            if (PresenceLookupRequired(notification))
            {
                string sipAddress = _factory.ObtainSipAddress(notification);

                if (sipAddress != null)
                {
                    // begin a presence lookup so that we can get the contact card information for any
                    // voice-based reminders as well as presence data for using to determine whether or
                    // not to send notifications with presence-based rules.
                    provider.GetPresenceAsync(notification, sipAddress);
                }
                else
                {
                    OperationalLogs.LogNoSipAddressError(notification.EmailAddress);

                    EventHandler<DeliveryCompletedEventArgs> callback = DeliverNotificationCompleted;

                    if (callback != null)
                    {
                        callback(this, new DeliveryCompletedEventArgs(
                            new NotificationResult(notification, NotificationResultStatus.Error,
                                NotificationChannelType.None)));
                    }
                }
            }
            else
            {
                BeginDelivery(provider, new PresenceObtainedEventArgs(notification, null));
            }
        }

        /// <summary>
        /// Determines if a presence lookup is required.
        /// </summary>
        private static bool PresenceLookupRequired(NotificationItem notification)
        {
            VoicemailNotificationItem voicemailNotification = notification as VoicemailNotificationItem;

            if (voicemailNotification != null)
            {
                return voicemailNotification.RequiresPresenceLookup();
            }
            else
            {
                return Helpers.AssertCast<CalendarNotificationItem>(notification).ReminderItem.RequiresLookup();
            }
        }

        /// <summary>
        /// Obtain a MessageSender and asynchronously begin sending the notification.
        /// </summary>
        /// <param name="sender">The IPresenceProvider used to do the contact card lookup</param>
        /// <param name="e">Contact card and presence info for the notification recipient</param>
        private void BeginDelivery(object sender, PresenceObtainedEventArgs e)
        {
            IPresenceProvider provider = Helpers.AssertCast<IPresenceProvider>(sender);
            provider.GetPresenceCompleted -= BeginDelivery;

            IMessageSender messageSender = _factory.CreateMessageSender(e.Notification, e);

            if (messageSender != null)
            {
                Logger.WriteTraceInformation(e.Notification.Context, "Beginning notification delivery.");

                messageSender.DeliverMessageCompleted += DeliverMessageCompleted;
                messageSender.DeliverMessageAsync();
            }
            else
            {
                Logger.WriteTraceInformation(e.Notification.Context, "DISAPTCHER: Notification not delivered because presence rules were not matched or no ReminderItems were present.");

                EventHandler<DeliveryCompletedEventArgs> callback = DeliverNotificationCompleted;

                if (callback != null)
                {
                    callback(this, new DeliveryCompletedEventArgs(new NotificationResult(
                        e.Notification, NotificationResultStatus.Error, NotificationChannelType.None)));
                }
            }
        }

        /// <summary>
        /// Event handler for completion of message delivery.
        /// </summary>
        private void DeliverMessageCompleted(object sender, DeliveryCompletedEventArgs e)
        {
            Debug.Assert(sender != null, "Sender should not be null for delivery callback");
            Debug.Assert(e != null, "e != null");
            Debug.Assert(e.Result != null, "e.Result != null");

            Helpers.AssertCast<IMessageSender>(sender).DeliverMessageCompleted -= DeliverMessageCompleted;
            EventHandler<DeliveryCompletedEventArgs> callback = DeliverNotificationCompleted;

            if (callback != null)
            {
                callback(this, e);
            }
        }
    }
}
